/*
Copyright 2008  Four Roads LLC conact@4-roads.com

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections;
using CommunityServer.Components;
using CommunityServer;
using System.Web.Caching;

namespace FourRoads.Links
{
	using DataProvider;

	/// <summary>
	/// Class that contains data about a particular section
	/// </summary>
	[Serializable()]
	public class ResLinkSection : Section	
	{
		private bool _IsPartial = false;
		private string _FreindlyPath = string.Empty;
		private string _FreindlySectionName = string.Empty;

        public string FreindlySectionName
        {
            get
            {
                if (_FreindlySectionName == string.Empty)
                {
                    _FreindlySectionName = Name.ToLower().Replace(" ", "_");
                }

                return _FreindlySectionName;
            }
        }

        public string FreindlyPath
        {
            get
            {
                if (_FreindlyPath == string.Empty)
                {
                    if (ParentID > 0)
                    {
                        ResLinkSection se = GetSection(ParentID);
                        _FreindlyPath = se.FreindlyPath;
                    }

                    _FreindlyPath += FreindlySectionName;

                    if (FreindlySectionName != string.Empty)
                        _FreindlyPath += "/";
                }

                return _FreindlyPath;
            }
        }

		//This will clear all of the section cache
		public static void ClearAllSectionCache()
		{
			CSCache.RemoveByPattern("ResLinks-Sections");
		}

		public bool IsUniqueInCurrentContext()
		{
			ResLinkSection sp = ResLinkSection.GetSection(ParentID);

			string formattedKey = null;
			ResLinkSection.GenerateAppKey(ParentID ,  Name, out formattedKey);

			foreach(ResLinkSection sc in sp.Sections)
			{
				if (string.Compare (sc.ApplicationKey, formattedKey , true) == 0)
				{
					return false;
				}
			}

			return true;
		}

		/// <summary>
		/// THis is an expensive call so we will try and cahce it use the key string
		/// ResLinks-Sections:s:all that way any changes will invliadate this
		/// </summary>
		/// <returns></returns>
		public static ArrayList GetAllSections()
		{
			string cacheKey = string.Format("ResLinks-Sections:s:all:user:{0}" , CSContext.Current.User.UserID);

			ArrayList sc = CSCache.Get(cacheKey) as ArrayList;
			if(sc == null)
			{
				ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();

				sc = rldp.GetAllActiveSections();

				//Now filter out the records that you don't have access to
				for(int i =0 ; i< sc.Count ;i++)
				{
					ResLinkSection sec = sc[i] as ResLinkSection;

					if (!ResLinkPermission.LightAccessCheck(sec , ResLinkPermissionEnum.ViewLink ,  CSContext.Current.User))
					{
						sc.RemoveAt(i);
						i--;
					}
				}

				CSCache.Insert(cacheKey , sc  , null , CSCache.HourFactor);
			}			


			return sc;
		}

		public static void GenerateAppKey(int parentSectionID , string name , out string formattedKey)
		{
			Globals.ValidateApplicationKey(name, out formattedKey);
			
			//formattedKey - Must be unique at parent level but can be repeated elsewhere
			//So if i combine the depth with the parent name this will make it unique in the right context
			ResLinkDataProvider dp = ResLinkDataProvider.Instance();
			ResLinkSection parent = ResLinkSection.GetSection(parentSectionID);
			ArrayList col = dp.GetParentBreadCrumbs(parent);

			formattedKey = string.Format("{0}_{1}_{2}" , formattedKey , col.Count , parent.Name);
		}

		//This will clear out any sections in the cach that
		public static void ClearSectionCache(int sectionID)
		{
			string cacheKey = string.Format("ResLinks-Sections:s:{0}" , sectionID);
			CSCache.RemoveByPattern(cacheKey);
		}

		public static void Delete(ResLinkSection section)
		{
			ResLinkDataProvider dp = ResLinkDataProvider.Instance();

			dp.DeleteSection(section);

			ClearSectionCache(section.ParentID);
			ClearSectionCache(section.SectionID);
		}

		public static ResLinkSection Add(ResLinkSection section)
		{
			ResLinkDataProvider dp = ResLinkDataProvider.Instance();

			section.SectionID = dp.InsertNewSection(section);

			ClearSectionCache(section.ParentID);

			return section;		
		}

		//Get a section from the either the cache or the database
		public static ResLinkSection Update(ResLinkSection section)
		{
			ResLinkDataProvider dp = ResLinkDataProvider.Instance();

			dp.UpdateSection(section);

			ClearSectionCache(section.SectionID);

			return section;
		}

		public static string CreateFriendlyNameCacheKey(string path)
		{
			return string.Format("ResLinks-Sections:s:Fr:{0}" , path);
		}

		//Get a section from the either the cache or the database
		public static ResLinkSection GetSection(int sectionID)
		{
			if (sectionID == -1)
				sectionID = 0;

			string cacheKey = string.Format("ResLinks-Sections:s:{0}" ,sectionID);

			ResLinkSection sc = CSCache.Get(cacheKey) as ResLinkSection;
			if(sc == null)
			{
				ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();

				sc = rldp.GetSectionDetails(sectionID);

				CSCache.Insert(CreateFriendlyNameCacheKey(sc.FreindlyPath) , sc.SectionID  , null , CSCache.DayFactor ,CacheItemPriority.High);

				//In order that we maintain the structure of cahce we need to make each item dependant on
				//its parent item
				CacheDependency dep = null;
				if (sc.ParentID > 0)
				{
					string paretnCacheKey = string.Format("ResLinks-Sections:s:{0}" ,sc.ParentID);
					dep = new CacheDependency(null , new string[]{paretnCacheKey});
				}

				CSCache.Insert(cacheKey , sc  , dep , CSCache.MinuteFactor,CacheItemPriority.Low);

				sc._IsPartial = false;
			}
			else
			{
				if (sc._IsPartial)
				{
					ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();

					rldp.GetSectionDetails(ref sc);

					sc._IsPartial = false;
				}
			}

			return sc;
		}

		public override string Url
		{
			get
			{
				return null;
			}
		}

		public ResLinkSection(int sectionID):base(sectionID)
		{
			_IsPartial = true;

			ApplicationType = (ApplicationType)ExApplicationType.ResLink;
		}

		public ResLinkSection():base()
		{
			ApplicationType = (ApplicationType)ExApplicationType.ResLink;
		}

		protected ArrayList _AdjustedSections = null;
		/// <summary>
		/// This gets the child sections that are active and if configured 
		/// the disabled sections as well
		/// </summary>
		public ArrayList AdjustedSections
		{
			get
			{
				ResLinkSection currentSection= this;

				if (_IsPartial)
					currentSection = ResLinkSection.GetSection(currentSection.SectionID);

				if (currentSection._AdjustedSections == null)
				{
					if (ResLinkConfiguration.Instance().HideDisabledSections == false)
					{
						return currentSection.Sections;
					}

					currentSection._AdjustedSections = new ArrayList();

					//Need to filter out the disabled ones
					foreach(ResLinkSection sc in currentSection.Sections)
					{
						if (sc.IsActive)
						{
							currentSection._AdjustedSections.Add(sc);
						}
					}
				}

				return currentSection._AdjustedSections;
			}
		}

		/// <summary>
		/// Each section type must return it's permission type
		/// </summary>
		public override PermissionBase DefaultRolePermission
		{
			get		
			{
				return new ResLinkPermission() as  PermissionBase;
			}
		}

		/// <summary>
		/// Provides the Method implementing the AccessCheckDelegate signature
		/// </summary>
		public override AccessCheckDelegate AccessCheck
		{
			get		
			{
				return new AccessCheckDelegate(ResLinkPermission.AccessCheck);
			}
		}

		/// <summary>
		/// Provides the Method implementing the ValidateDelegate signature
		/// </summary>
		public override ValidatePermissionsDelegate ValidatePermissions
		{
			get 
			{
				return new ValidatePermissionsDelegate(ResLinkPermission.Validate);
			}
		}


		public ResLinkLink FindLink(int postID)
		{
			ResLinkLink lnkFnd = null;
			if (Links != null)
			{
				Array.Sort(Links);

				int pos = Array.BinarySearch(Links , postID , null);

				if (pos > -1)
					lnkFnd = Links[pos];
			}

			return lnkFnd;
		}

		public static void ClearLinksCache(int sectionID)
		{
			string cacheKey = string.Format("ResLinks-Posts:s:{0}" , sectionID );

			CSCache.RemoveByPattern(cacheKey);
		}

		public static void ClearLinkCache(int sectionID , int postID)
		{
			string cacheKey = string.Format("ResLinks-Posts:s:{0}:p:{1}" , sectionID , postID);

			CSCache.RemoveByPattern(cacheKey);
		}

		private CacheDependency CacheIndividualLinks(ResLinkLink[] links)
		{
			ArrayList cacheStrings = new ArrayList();
			//This item will be dependant on the section not changing and the individual posts not changing
			//So we need to build up an array of cahce dependencies
			foreach(ResLinkLink lnk in links)
			{
				string cacheDependacy =  string.Format("ResLinks-Posts:s:{0}:p:{1}" ,SectionID , lnk.PostID );

				cacheStrings.Add(cacheDependacy);

				ResLinkLink sc = CSCache.Get(cacheDependacy) as ResLinkLink;
				if(sc == null)
				{
					CSCache.Insert(cacheDependacy , lnk  , null  , CSCache.MinuteFactor,CacheItemPriority.Low);
				}
			}

			CacheDependency dep = null;
		
			if (cacheStrings.Count > 0)
				dep = new CacheDependency( null , (string[])cacheStrings.ToArray(typeof(string)));

			return dep;
		}

		public ResLinkLinksSet GetPagedLinks( int page , int pageCount , LinkViewSort sort)
		{
			bool moderator = (ResLinkPermission.LightAccessCheck((ResLinkSection)this, (Permission)ResLinkPermissionEnum.Moderate , CSContext.Current.User ) 
				|| (ExtendedUserRoles.IsLinkAdministrator || CSContext.Current.User.IsModerator));

			//try and get the links ffrom the cache if that fails then get them from the database
			string cacheKey = string.Format("ResLinks-Posts:s:{0}:m:{1}:p:{2}:s:{3}"  , SectionID , moderator , page , sort.ToString());

			ResLinkLinksSet linksSet = CSCache.Get(cacheKey) as ResLinkLinksSet;

			if (linksSet == null)
			{
				ResLinkDataProvider rldp = ResLinkDataProvider.Instance();
				int moreRecords =0;

				ArrayList lnks = rldp.GetPagedLinks(SectionID , moderator , moderator , page , pageCount , sort , out moreRecords);

				ResLinkLink[] links = (ResLinkLink[]) lnks.ToArray(typeof(ResLinkLink));
  
				linksSet = new ResLinkLinksSet();
				linksSet.Page = page;
				linksSet.MoreRecords = moreRecords;
				linksSet.Links = links;
					
				CSCache.Insert(cacheKey , linksSet , CacheIndividualLinks(links) , CSCache.MinuteFactor , CacheItemPriority.Low);
			}

			return linksSet;
		}

		public ResLinkLink[] GetAllLinks(LinkViewSort sort)
		{
			bool moderator = (ResLinkPermission.LightAccessCheck((ResLinkSection)this, (Permission)ResLinkPermissionEnum.Moderate , CSContext.Current.User ) 
				|| (ExtendedUserRoles.IsLinkAdministrator || CSContext.Current.User.IsModerator));

			//try and get the links ffrom the cache if that fails then get them from the database
			string cacheKey = string.Format("ResLinks-Posts:s:{0}:m:{1}:s:{2}"  , SectionID , moderator , sort.ToString());

			ResLinkLink[] links = CSCache.Get(cacheKey) as ResLinkLink[];

			if (links == null)
			{
				ResLinkDataProvider rldp = ResLinkDataProvider.Instance();

				ArrayList lnks = rldp.GetLinks(SectionID , moderator , moderator , sort );

				links = (ResLinkLink[]) lnks.ToArray(typeof(ResLinkLink));

				CSCache.Insert(cacheKey , links , CacheIndividualLinks(links) , CSCache.MinuteFactor , CacheItemPriority.Low);
			}

			return links;
		}

		public ResLinkLink[] Links
		{
			get
			{
				return GetAllLinks(LinkViewSort.Name);
			}
		}


		private static string GenericQueryPostsCacheKey(string sectionID , string qType)
		{
			string _PostsCacheKey = "ResLinks-Posts:s:{0}:" + qType;
			
			return string.Format(_PostsCacheKey , sectionID);
		}

		protected delegate ArrayList GetItems(int top , int sectionID);

		protected static ArrayList GetGenericLinks(int top , int sectionID ,string cacheKeySt , GetItems getArray)
		{
			string key = GenericQueryPostsCacheKey(sectionID.ToString() , cacheKeySt);
			ArrayList items = CSCache.Get(key) as ArrayList;
			if(items == null)
			{
				items = new ArrayList();
				ArrayList itemIdx = getArray(top,sectionID);

				for(int i=0;i<itemIdx.Count;i++)
				{
					int[] item = itemIdx[i] as int[];

					ResLinkLink lnk = ResLinkLink.GetSingleLink(item[0] , item[1]);
					items.Add(lnk);
				}

				CSCache.Insert(key , items  ,  null , CSCache.MinuteFactor ,CacheItemPriority.Low);
			}

			return items;

		}

		public static ArrayList GetPopularLinks(int top , int sectionID)
		{
			ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();
			return GetGenericLinks(top , sectionID, "pop" , new GetItems(rldp.GetPopularLinks));
		}

		public static ArrayList GetRecentlyUpdatedLinks(int top , int sectionID)
		{
			ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();
			return GetGenericLinks(top , sectionID, "rec" , new GetItems(rldp.GetRecentlyUpdatedLinks));
		}


		public EmailSubscriptionType GetSectionSubscriptionType()
		{
			return ThreadTracking.GetSectionSubscriptionType(SectionID);
		}

		public void SetSectionSubscriptionType(EmailSubscriptionType newSub)
		{
			ThreadTracking.SetSectionSubscriptionType(SectionID , newSub);
		}

		public void DeleteLink(int postID)
		{
			ResLinkPermission.AccessCheck(this, ResLinkPermissionEnum.RemoveLinks , CSContext.Current.User );

			ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();

			ResLinkLink link = FindLink(postID);

			rldp.DeleteLink(link);

			if (link.IsApproved == false)
				ResLinkEmails.LinkDeleted(link);

			ClearLinksCache(SectionID);
		}
		
		public void ApproveLink(ResLinkLink link)
		{
			//Moderator just approve it
			ResLinkPermission.AccessCheck(this, ResLinkPermissionEnum.Moderate , CSContext.Current.User );

			bool previousState = link.IsApproved ;
			link.IsApproved = true;

			UpdateLink(link);

			if (previousState == false)
				ResLinkEmails.LinkApproved(link);

			ClearLinksCache(SectionID);
		}

		public void UpdateLink(ResLinkLink link)
		{
			ResLinkPermission.AccessCheck(this , ResLinkPermissionEnum.ViewLink , CSContext.Current.User);
			ResLinkPermission.AccessCheck(this , ResLinkPermissionEnum.EditLinks , CSContext.Current.User);

			ResLinkDataProvider rldp = ResLinkDataProvider.Instance();

			CSEvents.PrePost(link,ObjectState.Update,(ApplicationType)ExApplicationType.ResLink);
			CSEvents.BeforePost(link,ObjectState.Update,(ApplicationType)ExApplicationType.ResLink);

			rldp.UpdateLink(link);

			CSEvents.AfterPost(link,ObjectState.Update,(ApplicationType)ExApplicationType.ResLink);

			//Notify anyone watching this item
			ResLinkEmails.LinkTracking(link , this);

			ClearLinkCache(SectionID , link.PostID);
		}

		///
		///Must be able to add links to the destination and remove links from the source
		static public ResLinkLink MoveLink(ResLinkLink link , int newSectionID)
		{
			int oldSectionID = link.SectionID;
			ResLinkSection newSection = GetSection(newSectionID);
			ResLinkSection oldSection = GetSection(oldSectionID);

			ResLinkPermission.AccessCheck(newSection, ResLinkPermissionEnum.AddLinks , CSContext.Current.User );
			ResLinkPermission.AccessCheck(oldSection, ResLinkPermissionEnum.RemoveLinks , CSContext.Current.User );

			ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();

			CSEvents.PrePost(link , ObjectState.Update,(ApplicationType)ExApplicationType.ResLink);
			CSEvents.BeforePost(link , ObjectState.Update,(ApplicationType)ExApplicationType.ResLink);

			rldp.MoveLink(link , newSectionID);

			CSEvents.AfterPost(link,ObjectState.Update,(ApplicationType)ExApplicationType.ResLink);

			//Notify anyone watching this item
			ResLinkEmails.LinkTracking(link , newSection);

			ClearLinksCache(newSectionID);
			ClearLinkCache(oldSectionID , link.PostID);
			return link;
		}

		static public ResLinkLink AddLink(ResLinkSection section ,ResLinkLink link , int sectionID)
		{
			ResLinkPermission.AccessCheck(section, ResLinkPermissionEnum.AddLinks , CSContext.Current.User );
			//If this user is a moderator then no need to set it to approve
			link.IsApproved = ResLinkPermission.LightAccessCheck(section, ResLinkPermissionEnum.Moderate , CSContext.Current.User ) 
				|| (ExtendedUserRoles.IsLinkAdministrator || CSContext.Current.User.IsModerator)|| !section.IsModerated;

			link.SectionID = sectionID;
			link.User = CSContext.Current.User;

			ResLinkDataProvider  rldp = ResLinkDataProvider.Instance();

			CSEvents.PrePost(link,ObjectState.Create,(ApplicationType)ExApplicationType.ResLink);
			CSEvents.BeforePost(link,ObjectState.Create,(ApplicationType)ExApplicationType.ResLink);

			link.PostID = rldp.InsertNewLink(link);

			CSEvents.AfterPost(link,ObjectState.Create,(ApplicationType)ExApplicationType.ResLink);

			//Notify anyone watching this item
			ResLinkEmails.LinkTracking(link , section);

			ClearLinksCache(sectionID);

			return link;
		}

		private int _FullSubLinkCount = 0;
		public int FullSubLinkCount
		{
			get
			{
				return _FullSubLinkCount;
			}
			set
			{
				_FullSubLinkCount = value;
			}
		}

		#region Extended Properties

		public bool DisplayHitCounter
		{
			get
			{
				string val = GetExtendedAttribute( "DisplayHitCounter" );

				if(val != string.Empty)
					return bool.Parse(val);
				else
					return true;
			}
			set { SetExtendedAttribute( "DisplayHitCounter", value.ToString() ); }
		}

		public bool EnableSuggestLink
		{
			get
			{
				string val = GetExtendedAttribute( "EnableSuggestLink" );
	
				if(val != string.Empty)
					return bool.Parse(val);
				else
					return true;
			}
			set { SetExtendedAttribute( "EnableSuggestLink", value.ToString() ); }
		}

		public bool EnableEmailNotification
		{
			get
			{
				string val = GetExtendedAttribute( "EnableEmailNotification" );
		
				if(val != string.Empty)
					return bool.Parse(val);
				else
					return true;
			}
			set { SetExtendedAttribute( "EnableEmailNotification", value.ToString() ); }
		}

		public bool EnableComments
		{
			get
			{
				string val = GetExtendedAttribute( "EnableComments" );
		
				if(val != string.Empty)
					return bool.Parse(val);
				else
					return true;
			}
			set { SetExtendedAttribute( "EnableComments", value.ToString() ); }
		}

		public bool EnableRatings
		{
			get
			{
				string val = GetExtendedAttribute( "EnableRatings" );
		
				if(val != string.Empty)
					return bool.Parse(val);
				else
					return true;
			}
			set { SetExtendedAttribute( "EnableRatings", value.ToString() ); }
		}
		
		#endregion

	}
}
